using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Reflection;

namespace Common.Utilities
{
    /// <summary></summary>
    public  class XmlDataUtil
    {
        /// <summary></summary>
        public static XmlNode UpdateElemant(ref XmlDocument xdoc, string xpath, string elementName)
        {
            XmlNode node = xdoc.SelectSingleNode(xpath);
            if (node == null)
            {
                node = xdoc.CreateNode(XmlNodeType.Element, elementName, "");
                if (xpath.LastIndexOf("/") > 0)
                {
                    string parentPath = xpath.Substring(0, xpath.LastIndexOf("/"));
                    XmlNode parentNode = xdoc.SelectSingleNode(parentPath);
                    parentNode.AppendChild(node);
                }
                else
                {
                    xdoc.DocumentElement.AppendChild(node);
                }
            }
            return node;
        }

        /// <summary></summary>
        public static XmlNode UpdateElemant(ref XmlDocument xdoc, XmlNode parentNode, string elementName)
        {
            XmlNode node = null;
            if (parentNode.HasChildNodes)
            {
                foreach (XmlNode childNode in parentNode.ChildNodes)
                {
                    if (childNode.Name == elementName)
                    {
                        node = childNode;
                        break;
                    }
                }
            }
            if (node == null)
            {
                node = xdoc.CreateNode(XmlNodeType.Element, elementName, "");
                parentNode.AppendChild(node);
            }
            return node;
        }

        /// <summary></summary>
        public static XmlNode AddElement(ref XmlDocument xdoc, XmlNode parentNode, string elementName)
        {
            XmlNode node = xdoc.CreateNode(XmlNodeType.Element, elementName, "");
            parentNode.AppendChild(node);
            return node;
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="xdoc"></param>
		/// <param name="parentNode"></param>
		/// <param name="prefix"></param>
		/// <param name="namespaceUri"></param>
		/// <param name="elementName"></param>
		/// <returns></returns>
		public static XmlNode AddElementWithPrefix(ref XmlDocument xdoc, XmlNode parentNode, string prefix, string namespaceUri, string elementName)
		{
			XmlNode node = xdoc.CreateNode(XmlNodeType.Element, prefix, elementName, namespaceUri);
			parentNode.AppendChild(node);
			return node;
		}
		
        /// <summary></summary>
        public static XmlNode AddElement(ref XmlDocument xdoc, XmlNode parentNode, string elementName, string elementValue)
        {
            XmlNode node = xdoc.CreateNode(XmlNodeType.Element, elementName, "");
            parentNode.AppendChild(node);
            node.InnerText = elementValue;
            return node;
        }

        /// <summary></summary>
        public static XmlNode AddCdataElement(ref XmlDocument xdoc, XmlNode parentNode, string elementName, string cdataValue)
        {
            XmlNode node = xdoc.CreateNode(XmlNodeType.CDATA, elementName, "");
            parentNode.AppendChild(node);
            node.InnerText = cdataValue;
            return node;
        }

        /// <summary></summary>
        public static void UpdateAttribute(ref XmlDocument xdoc, XmlNode node, string attName, string attValue)
        {
            XmlAttribute att = (XmlAttribute)node.Attributes.GetNamedItem(attName);
            if (att == null)
            {
                att = xdoc.CreateAttribute(attName);
                node.Attributes.Append(att);
            }
            att.Value = attValue;
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="xdoc"></param>
		/// <param name="node"></param>
		/// <param name="prefix"></param>
		/// <param name="namespaceUri"></param>
		/// <param name="attName"></param>
		/// <param name="attValue"></param>
		public static void UpdateAttribute(ref XmlDocument xdoc, XmlNode node, 
			string prefix, string namespaceUri, string attName, string attValue)
		{
			XmlAttribute att = (XmlAttribute) node.Attributes.GetNamedItem(attName, namespaceUri);
			if(att==null)
			{
				att = xdoc.CreateAttribute(attName, namespaceUri);
				// att = xdoc.CreateAttribute(prefix, attName, namespaceUri);
				node.Attributes.Append(att);
			}
			att.Value = attValue;
		}

        /// <summary></summary>
        public static XmlAttribute GetAttribute(ref XmlDocument xdoc, XmlNode node, string attName, string defaultValue)
        {
            XmlAttribute att = (XmlAttribute)node.Attributes.GetNamedItem(attName);
            if (att == null)
            {
                att = xdoc.CreateAttribute(attName);
                att.Value = defaultValue;
                node.Attributes.Append(att);
            }
            return att;
        }

        /// <summary></summary>
        public static string GetAttributeValue(XmlNode xNode, string attName, string defaultValue)
        {
            if(xNode !=null && xNode.Attributes.GetNamedItem(attName)!=null)
            {
                XmlAttribute att = (XmlAttribute) xNode.Attributes.GetNamedItem(attName);
                return att.Value;
            }
            else
            {
                return defaultValue;
            }
        }

        /// <summary></summary>
        public static void UpdateAttributesWithInstanceProperties(ref XmlDocument xDoc, XmlNode node, object objInstance)
        {
            PropertyInfo[] props = objInstance.GetType().GetProperties();
            if (props != null && props.Length > 0)
            {
                for (int i = 0; i < props.Length; i++)
                {
                    string propName = props[i].Name;
                    Type propType = props[i].GetType();
                    object propValue = props[i].GetValue(objInstance, null);
                    XmlDataUtil.UpdateAttribute(ref xDoc, node, propName, GetStringRepresentationOfValue(propType, propValue));
                }
            }
        }

        /// <summary></summary>
        public static void UpdateAttributesWithInstanceProperties(ref XmlDocument xDoc, XmlNode node, Type objType, object objInstance)
        {
            PropertyInfo[] props = objType.GetProperties();
            if (props != null && props.Length > 0)
            {
                for (int i = 0; i < props.Length; i++)
                {
                    string propName = props[i].Name;
                    Type propType = props[i].GetType();
                    object propValue = props[i].GetValue(objInstance, null);
                    XmlDataUtil.UpdateAttribute(ref xDoc, node, propName, GetStringRepresentationOfValue(propType, propValue));
                }
            }
        }

        /// <summary></summary>
        public static object InstantiateObjectPropertiesFromAttributes(ref XmlDocument xDoc, XmlNode node, Type objType)
        {
            object objInstance = Activator.CreateInstance(objType);
            Dictionary<string, string> attNameValues = new Dictionary<string, string>();
            PropertyInfo[] props = objType.GetProperties();
            if (props != null && props.Length > 0 && node.Attributes.Count>0)
            {
                for (int i = 0; i < node.Attributes.Count; i++)
                {
                    string attName = node.Attributes[i].Name;
                    string attValue = node.Attributes[i].Value;
                    attNameValues.Add(attName, attValue);
                }
                for (int i = 0; i < props.Length; i++)
                {
                    string propName = props[i].Name;
                    if (attNameValues.ContainsKey(propName))
                    {
                        Type propType = props[i].GetType();
                        object propValue = GetValueFromItsStringRepresentation(propType, attNameValues[propName]);
                        props[i].SetValue(objInstance, propValue, null);
                    }
                    else
                    {
                        props[i].SetValue(objInstance, null, null);
                    }
                }
            }
            return objInstance;
        }

        private static string GetStringRepresentationOfValue(Type type, object value)
        {
            if(type.GetType()==typeof(int[]))
            {
                int[] intVals = value as int[];
                return IntUtil.ToString(intVals);
            }
            else if (type.GetType() == typeof(string[]))
            {
                string[] strVals = value as string[];
                return StringUtil.ToString(strVals);
            }
            else if (type == typeof(System.Drawing.Image))
            {
                return ImageUtil.SerializeImage((System.Drawing.Image)value);
            }
            else if (type == typeof(System.Drawing.Icon))
            {
                return ImageUtil.SerializeIcon((System.Drawing.Icon)value);
            }
            else
            {
                return value.ToString();
            }
        }

        private static object GetValueFromItsStringRepresentation(Type type, string value)
        {
            if (type.GetType() == typeof(string[]))
            {
                string[] strVals = StringUtil.Split(value);
                return strVals;
            }
            else if (type.GetType() == typeof(int[]))
            {
                int[] intVals = IntUtil.ToArray(value);
                return intVals;
            }
            else
            {
                if (type == typeof(string))
                {
                    return value;
                }
                else if (type == typeof(int))
                {
                    return int.Parse(value);
                }
                else if (type == typeof(Int16))
                {
                    return Int16.Parse(value);
                }
                else if (type == typeof(Int32))
                {
                    return Int32.Parse(value);
                }
                else if (type == typeof(Int64))
                {
                    return Int64.Parse(value);
                }
                else if (type == typeof(Single))
                {
                    return Single.Parse(value);
                }
                else if (type == typeof(long))
                {
                    return long.Parse(value);
                }
                else if (type == typeof(float))
                {
                    return float.Parse(value);
                }
                else if (type == typeof(decimal))
                {
                    return decimal.Parse(value);
                }
                else if (type == typeof(double))
                {
                    return double.Parse(value);
                }
                else if (type == typeof(DateTime))
                {
                    return DateTime.Parse(value);
                }
                else if (type == typeof(bool))
                {
                    return bool.Parse(value);
                }
                else if (type == typeof(Guid))
                {
                    return new Guid(value);
                }
                else if (type == typeof(System.Drawing.Image))
                {
                    return ImageUtil.ToImage(value);
                }
                else if (type == typeof(System.Drawing.Icon))
                {
                    return ImageUtil.ToIcon(value);
                }
                else
                {
                    return value;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static string GetCdataValue(XmlNode node, string defaultValue)
        {
            return node != null ? node.InnerText : defaultValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlFilePath"></param>
        /// <returns></returns>
        public static List<string> GetDistinctElementPaths(string xmlFilePath)
        {
            XmlTextReader xReader = new XmlTextReader(File.OpenRead(xmlFilePath));
            List<string> allPaths = new List<string>();
            int lastDepth = -1;
            string lastNodeName = string.Empty;
            Stack<string> elementNames = new Stack<string>();
            while (xReader.Read())
            {
                if (xReader.NodeType == XmlNodeType.Element)
                {
                    int currentDepth = xReader.Depth;
                    if (currentDepth > lastDepth)
                    {
                        lastDepth = currentDepth;
                        lastNodeName = xReader.Name;
                        elementNames.Push(lastNodeName);
                        string xPath = XmlDataUtil.GetXPath(elementNames);
                        if (!allPaths.Contains(xPath))
                            allPaths.Add(xPath);
                    }
                    else if (currentDepth == lastDepth)
                    {
                        if (xReader.Name == lastNodeName)
                            continue;
                        else
                        {
                            lastNodeName = xReader.Name;
                            if (elementNames.Count > 0)
                                elementNames.Pop();
                            elementNames.Push(lastNodeName);
                            string xPath = XmlDataUtil.GetXPath(elementNames);
                            if (!allPaths.Contains(xPath))
                                allPaths.Add(xPath);
                        }
                    }
                    else
                    {
                        lastDepth = xReader.Depth;
                        lastNodeName = xReader.Name;
                        while (elementNames.Count > xReader.Depth)
                            elementNames.Pop();
                        elementNames.Push(lastNodeName);
                        string xPath = XmlDataUtil.GetXPath(elementNames);
                        if (!allPaths.Contains(xPath))
                            allPaths.Add(xPath);
                    }
                }
                else if (xReader.NodeType == XmlNodeType.EndElement)
                {

                }
            }
            xReader.Close();
            
            return allPaths;
        }

        private static string GetXPath(Stack<string> elementNames)
        {
            string[] elementNamesCopy = elementNames.ToArray();
            string path = string.Empty;
            for (int i = elementNamesCopy.Length - 1; i >= 0; i--)
            {
                if (path.Length > 0)
                    path += "/";
                path += elementNamesCopy[i];
            }
            return path;
        }
    }
}
